﻿using GTI = Gadgeteer.SocketInterfaces;

namespace Gadgeteer.Modules.SolderMonkey
{
    /// <summary>
    ///     A Encoder module for Microsoft .NET Gadgeteer
    /// </summary>
    public class Encoder : Module
    {
        /// <summary>
        ///     Represents the delegate that is used to handle the
        ///     <see cref="E:Gadgeteer.Modules.SolderMonkey.Encoder.ButtonPressed" />
        ///     and <see cref="E:Gadgeteer.Modules.SolderMonkey.Encoder.ButtonReleased" /> events.
        /// </summary>
        /// <param name="sender">The <see cref="T:Gadgeteer.Modules.SolderMonkey.Encoder" /> object that raised the event.</param>
        /// <param name="state">The state of the button of the <see cref="T:Gadgeteer.Modules.LoveElectronics.Button" /></param>
        public delegate void ButtonEventHandler(Encoder sender, ButtonState state);

        /// <summary>
        ///     Represents the delegate that is used to handle the <see cref="!:PositiveStep" />
        ///     and <see cref="!:NegativeStep" /> events.
        /// </summary>
        /// <param name="sender">The <see cref="T:Gadgeteer.Modules.SolderMonkey.Encoder" /> object that raised the event.</param>
        /// <param name="dir">The state of the button of the <see cref="T:Gadgeteer.Modules.SolderMonkey.Encoder" /></param>
        public delegate void StepEventHandler(Encoder sender, StepDirection dir);

        /// <summary>
        ///     Represents the state of button of the <see cref="T:Gadgeteer.Modules.SolderMonkey.Encoder" />.
        /// </summary>
        public enum ButtonState
        {
            Released,
            Pressed,
        }

        /// <summary>
        ///     Represents the direction of rotation for the most recent Step event
        ///     <see cref="T:Gadgeteer.Modules.SolderMonkey.Encoder" />.
        /// </summary>
        public enum StepDirection
        {
            Pos,
            Neg,
        }

        private readonly GTI.InterruptInput _buttonInput;

        //private readonly GTI.DigitalInput _buttonPin;
        private readonly GTI.DigitalInput _dirPin;
        private ButtonEventHandler _onButton;
        private StepEventHandler _onStep;
        private GTI.InterruptInput stepPin;

        /// <summary />
        /// <param name="socketNumber">The socket that this module is plugged in to.</param>
        public Encoder(int socketNumber)
        {
            Socket socket = Socket.GetSocket(socketNumber, true, this, null);

            socket.EnsureTypeIsSupported(new[] {'X', 'Y'}, this);

            stepPin = GTI.InterruptInputFactory.Create(socket, Socket.Pin.Three, GTI.GlitchFilterMode.On,
                GTI.ResistorMode.PullUp, GTI.InterruptMode.RisingEdge, this);
            _dirPin = GTI.DigitalInputFactory.Create(socket, Socket.Pin.Five, GTI.GlitchFilterMode.On,
                GTI.ResistorMode.PullUp, this);
            stepPin.Interrupt += _input_Interrupt;
            _buttonInput = GTI.InterruptInputFactory.Create(socket, Socket.Pin.Four, GTI.GlitchFilterMode.On,
                GTI.ResistorMode.PullUp, GTI.InterruptMode.RisingAndFallingEdge, this);
            _buttonInput.Interrupt += ButtonInput_Interrupt;
        }

        /// <summary>
        ///     Gets a value that indicates whether the button of the Button is pressed.
        /// </summary>
        public bool IsPressed
        {
            get { return _buttonInput.Read(); }
        }

        /// <summary>
        ///     Gets a value that indicates the last direction turned.
        /// </summary>
        public StepDirection LastStepDirection()
        {
            return _dirPin.Read() ? StepDirection.Pos : StepDirection.Neg;
        }

        //public bool IsPressed
        //{
        //    get { return _buttonPin.Read(); }
        //}
        /// <summary>
        ///     Gets a value that indicates whether the button of the Encoder is pressed.
        /// </summary>
        /// <summary>
        ///     Raised when the shaft of the <see cref="T:Gadgeteer.Modules.SolderMonkey.Encoder" /> is rotated.
        /// </summary>
        /// <remarks>
        ///     Implement this event handler and/or the <see cref="!:NegativeStep" /> event handler
        ///     when you want to provide an action associated with step events.
        /// </remarks>
        public event StepEventHandler Step;

        private void _input_Interrupt(GTI.InterruptInput input, bool value)
        {
            OnStepEvent(this, _dirPin.Read() ? StepDirection.Pos : StepDirection.Neg);
        }

        /// <summary>
        ///     Raises the <see cref="!:PositiveStep" /> or <see cref="!:NegativeStep" /> event.
        /// </summary>
        /// <param name="sender">The <see cref="T:Gadgeteer.Modules.SolderMonkey.Encoder" /> that raised the event.</param>
        /// <param name="stepDir">The state of the button.</param>
        protected virtual void OnStepEvent(Encoder sender, StepDirection stepDir)
        {
            if (_onStep == null)
                _onStep = OnStepEvent;
            if (!Program.CheckAndInvoke(Step, _onStep, (object) sender, (object) stepDir))
                return;
            Step(sender, stepDir);
        }

        private void ButtonInput_Interrupt(GTI.InterruptInput input, bool value)
        {
            OnButtonEvent(this, value ? ButtonState.Released : ButtonState.Pressed);
        }

        /// <summary>
        ///     Raised when the button of the <see cref="T:Gadgeteer.Modules.SolderMonkey.Encoder" /> is pressed.
        /// </summary>
        /// <remarks>
        ///     Implement this event handler and/or the <see cref="E:Gadgeteer.Modules.SolderMonkey.Encoder.ButtonReleased" />
        ///     event handler
        ///     when you want to provide an action associated with button events.
        ///     Since the state of the button is passed to the
        ///     <see cref="T:Gadgeteer.Modules.SolderMonkey.Encoder.ButtonEventHandler" /> delegate,
        ///     so you can use the same event handler for both button states.
        /// </remarks>
        public event ButtonEventHandler ButtonPressed;

        /// <summary>
        ///     Raised when the button of the <see cref="T:Gadgeteer.Modules.SolderMonkey.Encoder" /> is released.
        /// </summary>
        /// <remarks>
        ///     Implement this event handler and/or the <see cref="E:Gadgeteer.Modules.SolderMonkey.Encoder.ButtonPressed" />
        ///     event handler
        ///     when you want to provide an action associated with button events.
        ///     Since the state of the button is passed to the
        ///     <see cref="T:Gadgeteer.Modules.SolderMonkey.Encoder.ButtonEventHandler" /> delegate,
        ///     you can use the same event handler for both button states.
        /// </remarks>
        public event ButtonEventHandler ButtonReleased;

        /// <summary>
        ///     Raises the <see cref="E:Gadgeteer.Modules.SolderMonkey.Encoder.ButtonPressed" /> or
        ///     <see cref="E:Gadgeteer.Modules.LoveElectronics.Button.ButtonReleased" /> event.
        /// </summary>
        /// <param name="sender">The <see cref="T:Gadgeteer.Modules.SolderMonkey.Encoder" /> that raised the event.</param>
        /// <param name="buttonState">The state of the button.</param>
        protected virtual void OnButtonEvent(Encoder sender, ButtonState buttonState)
        {
            if (_onButton == null)
                _onButton = OnButtonEvent;
            if (buttonState == ButtonState.Pressed)
            {
                if (!Program.CheckAndInvoke(ButtonPressed, _onButton, (object) sender, (object) buttonState))
                    return;
                ButtonPressed(sender, buttonState);
            }
            else
            {
                if (!Program.CheckAndInvoke(ButtonReleased, _onButton, (object) sender, (object) buttonState))
                    return;
                ButtonReleased(sender, buttonState);
            }
        }
    }
}